package mini_game_fx;

//import java.awt.event.ActionEvent;
//import java.awt.event.ActionListener;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javax.swing.event.HyperlinkEvent;
/**
 * Sprite objects represent a visible game entity, which
 * may be a game character, enemy, or button. Sprites can
 * register ActionListeners and thus test for and respond
 * to mouse presses on them. Note that Sprite objects store
 * data particular to single visible entity, and that multiple
 * Sprites will share a single SpriteType, which refers to 
 * shared characteristics (like artwork) among those Sprites.
 * 
 * @author Richard McKenna
 * @version 1.0
 */
public class SpriteFx 
{
	// WHAT TYPE OF SPRITE IS IT? NOTE THE SpriteType OBJECT
	// HAS ALL THE ARTWORK FOR THE SPRITE. THE REASON IS IF
	// WE HAVE 50 IDENTICAL ORCs, WE ONLY WANT THEIR IMAGES
	// LOADED ONCE, BUT EACH WOULD HAVE INDIVIDUAL CHARACTERISITCS
	// LIKE POSITION, VELOCITY, ETC.
	protected SpriteTypeFx spriteType;
	
	// CURRENT GAME WORLD/CANVAS LOCATION OF this SPRITE
	protected double x;
	protected double y;
	
	// CURRENT GAME WORLD/CANVAS VELCOTIY OF this SPRITE. NOTE
	// THAT VELOCITY REFERS TO PIXELS TO BE MOVED PER FRAME
	protected double vX;
	protected double vY;
	
	// CURRENT STATE OF this SPRITE, IT DICTATES WHICH IMAGE
	// SHOULD BE USED FOR RENDERING IT
	protected String state;

	// AABB REFERS TO AXIS-ALIGNED-BOUNDING-BOX, WHICH IS A LONG
	// WAY OF SAYING A SQUARE COLLISION BOX. IN SOME GAMES THE
	// COLLISION BOX MAY NEED TO BE SMALLER THAN THE ARTWORK, AND
	// MAY EVEN CHANGE PERIODICALLY.
	protected double aabbX;
	protected double aabbY;
	protected double aabbWidth;
	protected double aabbHeight;

	// THE CUSTOM EVENT HANDLER FOR THIS Sprite WHOSE actionPerformed
	// METHOD IS TO INVOKED WHEN THE PLAYER CLICKS ON THIS Sprite
	private EventHandler<MouseEvent> mouseHandler;
	private EventHandler<KeyEvent> keyHandler;
	
	// ID NUMBER OF THIS Sprite, THIS IS AUTOMATICALLY ASSIGNED UPON
	// Sprite CONSTRUCTION. NOTE THAT THIS IS A READ-ONLY VALUE
	private int id;
	
	// USED FOR ASSIGNING ID NUMBERS TO SPRITES. EACH TIME ANOTHER
	// Sprite OBJECT IS CONSTRUCTED, WE INCREMENT THIS COUNTER, THUS
	// GETTING A NEW, UNIQUE NUMBER
	private static int idCounter;
        public String currentState;
        public String previousState;
        
        private boolean added;

	/**
	 * Constructor for making a new Sprite with an initial position,
	 * velocity, state, etc. Note that a unique, accessible ID number
	 * is automatically assigned to each constructed Sprite.
	 * 
	 * @param initSpriteType the SpriteType of this Sprite object, it
	 * dictates what artwork will be used for rendering it.
	 * 
	 * @param initX initial X-axis position of this Sprite.
	 * 
	 * @param initY initial Y-axis position of this Sprite.
	 * 
	 * @param initVx initial X-axis velocity of this Sprite.
	 * 
	 * @param initVy initial Y-axis velocity of this Sprite.
	 * 
	 * @param initState initial state of this Sprite, which
	 * determines which of the possible images to use in
	 * rendering
	 */
	public SpriteFx(	SpriteTypeFx initSpriteType,
			double initX, 	double initY,
			double initVx, 	double initVy,
			String initState)
	{
		// INIT WITH THE PARAMETERS PROVIDED
		spriteType = initSpriteType;
		x = initX;
		y = initY;
		vX = initVx;
		vY = initVy;
		state = initState;
		id = idCounter;

		// CHANGE THE ID COUNTER FOR THE NEXT Sprite
		idCounter++;

		// BY DEFAULT THE AABB FITS THE SPRITE. NOTE THAT
		// ONE MAY CHANGE THIS USING THE SET METHODS
		aabbX = 0;
		aabbY = 0;
		aabbWidth = spriteType.getWidth();
		aabbHeight = spriteType.getHeight();
                added = false;
                currentState = "";
                previousState = "";
	}
	
	// ACCESSOR METHODS
		// getSpriteType
		// getAABBx
		// getAABBy
		// getAABBwidth
		// getAABBheight
		// getX
		// getY
		// getVx
		// getVy
		// getState
		// getID

	/**
	 * For accessing the axis-aligned bounding box' x-axis coordinate
	 * 
	 * @return the aabb's x value
	 */
	public double getAABBx() { return aabbX; }
	
	/**
	 * For accessing the axis-aligned bounding box' y-axis coordinate
	 * 
	 * @return the aabb's y value
	 */
	public double getAABBy() { return aabbY; }

	/**
	 * For accessing the axis-aligned bounding box' width
	 * 
	 * @return the aabb's width
	 */
	public double getAABBwidth() { return aabbWidth; }

	/**
	 * For accessing the axis-aligned bounding box' height
	 * 
	 * @return the aabb's height
	 */
	public double getAABBheight() { return aabbHeight; }
	
	/**
	 * For accessing the SpriteType object.
	 * 
	 * return the SpriteType of this sprite.
	 */
	public SpriteTypeFx getSpriteType() { return spriteType; 	}
	
	/**
	 * For accessing the X-axis coordinate of this Sprite.
	 * 
	 * return the x-axis location of this Sprite according to
	 * the top-left corner of the image used.
	 */	
	public double getX()	{ return x;	}
	
	/**
	 * For accessing the Y-axis coordinate of this Sprite.
	 * 
	 * @return the y-axis location of this Sprite according to
	 * the top-left corner of the image used.
	 */
	public double getY()	{ return y;	}

	/**
	 * For accessing the X-axis velocity of this Sprite. Note that
	 * velocity refers to pixels (game world units) moved each frame.
	 * 
	 * @return the x-axis velocity of this Sprite.
	 */
	public double getVx() { return vX; }
	
	/**
	 * For accessing the Y-axis velocity of this Sprite. Note taht 
	 * velocity refers to pixels (game world units) moved each frame.
	 * 
	 * @return the y-axis velocity of this Sprite.
	 */
	public double getVy() { return vY; }
	
	/**
	 * For accessing the current state of this Sprite. The state
	 * determines which image is used for rendering.
	 * 
	 * @return the current Sprite state.
	 */
	public String getState() { return state; }
	
	/**
	 * For accessing the automatically generated ID number of
	 * this Sprite object.
	 * 
	 * @return the ID number of this Sprite. Note that each
	 * Sprite has a unique id, which may or many not be used
	 * by the game application developer for things like
	 * event handling.
	 */
	public int getID()	{ return id; }
	
	// MUTATOR METHODS
		// setActionListener
		// setSpriteType
		// setX
		// setY
		// setVx
		// setVy
		// setState

	/**
	 * Mutator method for specifying this Sprite's event handler
	 * for when the player clicks on it. Note addActionListener
	 * was deliberately used to avoid confusion, since each Sprite
	 * may only have one ActionListener
	 * 
	 * param initListener the event handler to register for this
	 * sprite to be associated with mouse presses within its bounds.
	 */
	public void setMouseActionListener(EventHandler<MouseEvent> initListener)
	{
		mouseHandler = initListener;
	}	
	
        public EventHandler<MouseEvent> getMouseActionListener()
	{
		return mouseHandler;
	}
        
	public void setKeyActionListener(EventHandler<KeyEvent> initListener)
	{
		keyHandler = initListener;
	}
        
        
        public EventHandler<KeyEvent> getKeyActionListener()
	{
		return keyHandler;
	}
        
        public boolean getAdded(){
            return added;
        }
        
        public void setAdded(boolean added){
            this.added = added;
        }

	/**
	 * Mutator method for setting the type of Sprite this is.
	 * 
	 * @param initSpriteType the SpriteType to use for rendering
	 * this sprite.
	 */
	public void setSpriteType(SpriteTypeFx initSpriteType)
	{
		spriteType = initSpriteType;
	}
	
	/**
	 * Mutator method for setting the x-axis location of this Sprite.
	 * 
	 * @param initX the x-axis location to move this Sprite to. Note
	 * that if a value less than 0 or greater than the game world width
	 * is provided the Sprite may no longer be visible, since it won't
	 * be in the game canvas.
	 */
	public void	setX(double initX)			{ 	x = initX; 			}
	
	/**
	 * Mutator method for setting the y-axis location of this Sprite.
	 * 
	 * @param initY the y-axis location to move this Sprite to. Note
	 * that if a value less than 0 or greater than the game world height
	 * is provided the Sprite may no longer be visible, since it won't
	 * be in the game canvas.
	 */
	public void setY(double initY)			{ 	y = initY;			}
	
	/**
	 * Mutator method for setting the x-axis velocity of this Sprite.
	 * 
	 * @param initVx the x-axis velocity to be used. Note that this refers
	 * to the amount to move this Sprite each frame in the x-axis.
	 */
	public void setVx(double initVx)			{ 	vX = initVx;		}
	
	/**
	 * Mutator method for setting the y-axis velocity of this Sprite.
	 * 
	 * @param initVy the y-axis velocity to be used. Note that this refers
	 * to the amount to move this Sprite each frame in the y-axis.
	 */
	public void setVy(double initVy)			{	vY = initVy;		}
	
	/**
	 * Mutator method for setting the state of this Sprite.
	 * 
	 * @param initState the state to use for this Sprite, which will determine
	 * which image to use for rendering. Note that one should only set the
	 * state to one of the states made available by this Sprite's SpriteType.
	 * Using a different will result in error.
	 */
	public void setState(String initState)	{	state = initState;	}
		
	// ADDITIONAL METHODS
		// aabbsOverlap
		// calculateDistanceToSprite
		// containsPoint
		// testForClick
		// update
	
	/**
	 * This method tests to see if the the testSprite's AABB overlaps
	 * this Sprite's AABB. true is returned if they overlap, false
	 * otherwise.
	 * 
	 * return true if this Sprite's bounding box overlaps the testSprite's
	 * bounding box. false is returned otherwise.
	 */
	public boolean aabbsOverlap(SpriteFx testSprite)
	{
		if ((x + aabbX) > (testSprite.x + testSprite.aabbX + testSprite.aabbWidth))
			return false;
		else if ((x + aabbX + aabbWidth) < (testSprite.x + testSprite.aabbX))
			return false;
		else if ((y + aabbY) > (testSprite.y + testSprite.aabbY + testSprite.aabbHeight))
			return false;
		else if ((y + aabbY + aabbHeight) < (testSprite.y + testSprite.aabbY))
			return false;
		else
			return true;
	}
	
	/**
	 * This method calculates and returns the real number distance
	 * from this sprite to the targetSprite using the top-left corner
	 * of their AABBs as points.
	 *  
	 * @param targetSprite the Sprite to measure the distance to
	 * 
	 * @return a scalar representing the direct distance from this
	 * Sprite's AABB to the targetSprite's AABB.
	 */
	public double calculateDistanceToSprite(SpriteFx targetSprite)
	{
		double targetSpriteCenterX = targetSprite.x + targetSprite.aabbX + (targetSprite.aabbWidth/2);
		double targetSpriteCenterY = targetSprite.y + targetSprite.aabbY + (targetSprite.aabbHeight/2);
		
		double centerX = x + aabbX + (aabbWidth/2);
		double centerY = y + aabbY + (aabbHeight/2);
		
		double deltaX = targetSpriteCenterX - centerX;
		double deltaY = targetSpriteCenterY - centerY;
		
		return (double)Math.sqrt((deltaX*deltaX) + (deltaY*deltaY));
	}
	
	/**
	 * This method tests to see if the point represented by 
	 * pointX, pointY is inside this Sprite's AABB. If it is,
	 * true is returned, otherwise false is.
	 * 
	 * @param pointX - the x-axis coordinate of the test point
	 * 
	 * @param pointY - the y-axis coordinate of the test point
	 * 
	 * @return true if the point is inside this Sprite's AABB,
	 * false otherwise. Note that the AABB's borders are considered
	 * inside the box.
	 */
	public boolean containsPoint(double pointX, double pointY)
	{
		// FIRST MOVE THE POINT TO LOCAL COORDINATES
		pointX = pointX - x;
		pointY = pointY - y;
		
		boolean inXRange = false;
		if ((pointX > aabbX) && (pointX < (aabbX + aabbWidth)))
			inXRange = true;
		boolean inYRange = false;
		if ((pointY > aabbY) && (pointY < (aabbY + aabbHeight)))
			inYRange = true;
		return inXRange && inYRange;
	}	
	
	/**
	 * This method tests to if the x,y coordinates are inside
	 * this Sprite, and if they are, the registered event listener
	 * is invoked, making sure that a lock is first obtained on
	 * the data such that necessary updates may be made.
	 *  
	 * @param game the game application being used.
	 * 
	 * @param x the x-axis coordinate of the point being tested.
	 * 
	 * @param y the y-axis coordinate of the point being tested.
	 * 
	 * @return true if the point was inside the Sprite and an event
	 * handler is invoked in response. false otherwise, which means
	 * false will be returned if the point is inside the Sprite, but
	 * no event handler is registered for this Sprite.
	 */
	public boolean testForClick(MiniGameFx game, int x, int y)
	{
            System.out.println("mousehandler = " + mouseHandler);
		if (containsPoint(x,y) && (mouseHandler != null))
		{
//			EventHandler<MouseEvent> ae;
			MouseEvent ae;
//			ae = new MouseEvent(this,null); 
			ae = new MouseEvent(MouseEvent.MOUSE_CLICKED, x - game.primaryStage.getScene().getX(), y - game.primaryStage.getScene().getY(), x, y, 
                                MouseButton.NONE, 1, false, false, false, false, true, false, false, true, true, true, null); 
                        /*
                        Parameters:
                                eventType - The type of the event. 
                                x - The x with respect to the scene. 
                                y - The y with respect to the scene. 
                                screenX - The x coordinate relative to screen. 
                                screenY - The y coordinate relative to screen. 
                                button - the mouse button used clickCount - number of click counts shiftDown - true if shift modifier was pressed. 
                                clickCount - number of click counts 
                                shiftDown - true if shift modifier was pressed. 
                                controlDown - true if control modifier was pressed. 
                                altDown - true if alt modifier was pressed. 
                                metaDown - true if meta modifier was pressed. '
                                primaryButtonDown - true if primary button was pressed. 
                                middleButtonDown - true if middle button was pressed. 
                                secondaryButtonDown - true if secondary button was pressed. 
                                synthesized - if this event was synthesized 
                                popupTrigger - whether this event denotes a popup trigger for current platform 
                                stillSincePress - see MouseEvent.isStillSincePress() pickResult - pick result. 
                                Can be null, in this case a 2D pick result without any further values is constructed based on the scene coordinates
                        */
                            //public MouseEvent(EventType<? extends MouseEvent> eventType, double x, double y, double screenX, double screenY, MouseButton button, int clickCount, boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown, boolean primaryButtonDown, boolean middleButtonDown, boolean secondaryButtonDown, boolean synthesized, boolean popupTrigger, boolean stillSincePress, PickResult pickResult)
                        
//Evaluate above line for changes to the target.
			game.beginUsingData();
			mouseHandler.handle(ae);
			game.endUsingData();
			return true;
		}
		else
			return false;
	}	
	
	/**
	 * Called once per frame, this method does a simple update for
	 * this sprite, moving its position according to its current
	 * velocity, making sure it doesn't leave the playing area. Note
	 * that this method should be overridden to provide custom
	 * AI or physics behaviour for a particular type of Sprite.
	 * 
	 * @param game
	 */
	public void update(MiniGameFx game)
	{
		// MOVE THE SPRITE USING ITS VELOCITY
		x += vX;
		y += vY;
		
		// AND CLAMP IT IF IT WENT OUT OF BOUNDS FIRST ON X-AXIS
		if (x < game.getBoundaryLeft())
			x = game.getBoundaryLeft();
		else if ((x + spriteType.getWidth()) > game.getBoundaryRight())
			x = game.getBoundaryRight() - spriteType.getWidth();

		// AND THEN THE Y-AXIS
		if (y < game.getBoundaryTop())
			y = game.getBoundaryTop();
		else if ((y + spriteType.getHeight() > game.getBoundaryBottom()))
			y = game.getBoundaryBottom() - spriteType.getHeight();
	}
}